package com.vecspace.web.servlet;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.vecspace.core.support.AppProperties;
import com.vecspace.data.model.IPagination;
import com.vecspace.data.model.ListBO;
import com.vecspace.data.model.ResultObject;
import com.vecspace.msoffice.ExcelTemplate;
import com.vecspace.msoffice.ExportToHSSF;
import com.vecspace.security.model.SSOConsts;
import com.vecspace.web.servlet.model.*;
import org.apache.commons.lang3.StringUtils;

import com.vecspace.core.support.BeansContextUtil;
import com.vecspace.data.model.reflect.ClassSimpleInfo;
import com.vecspace.exception.HException;
import com.vecspace.exception.RestException;
import com.vecspace.security.model.LoginContext;
import com.vecspace.utils.LocalMachine;
import com.vecspace.utils.TJSON;
import com.vecspace.utils.UUIDUtil;
import com.vecspace.utils.log.HLogClient;
import com.vecspace.utils.log.HLogger;
import com.vecspace.utils.log.HttpRequestLog;
import com.vecspace.utils.log.HttpRequestLogThreadLocal;
import com.vecspace.web.HttpContentType;

/** irest请求的统一入口
 * @author: wanghua
 */
public class BaseRestServlet extends HttpServlet{

	private static final long serialVersionUID = 28595536384779654L;
	
	private static String charset = "utf-8";
		
	private String indexName= "index";//默认页面
	
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		this.doServlet(request, response, "GET");
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		this.doServlet(request, response,"POST");
	}

	private void doServlet(HttpServletRequest request, HttpServletResponse response, String httpMethod){
		String contentType = "text/html";

		LoginContext.clear();
		LoginContext.setLoginContextFromSession();

		UrlInfo urlInofo = new UrlInfo(request);
		String resultText = null;
		try {
			ServletResult servletResult = this.excuteRequest(urlInofo.getResType(), urlInofo.getBeanName(), urlInofo.getPathName(), request, response, httpMethod);
			if(servletResult == null || "file".equals(servletResult.getResponseDataType())) return;
			Object resultObj = servletResult.getResponseData();
			contentType = servletResult.getContentType();
			resultText = trans2ResponseText(request, response, contentType, resultObj);
		} catch (HException e1) {
			response.setStatus(400);
			RestException err = new RestException(e1.getErrCode(), e1.getMessage());
			try {
				resultText = TJSON.toJSONString(err);
			} catch (JsonProcessingException e) {
				e.printStackTrace();
			}
			contentType = "application/json;charset="+charset;
			response.setContentType(contentType);
			HLogger.DEFAULT().error(e1);
		} catch (Exception e2) {
			response.setStatus(400);
			RestException err = new RestException(e2);
			err.setErrorDetail(e2.toString());
			try {
				resultText = TJSON.toJSONString(err);
			} catch (JsonProcessingException e) {
				e.printStackTrace();
			}
			contentType = "application/json;charset="+charset;
			response.setContentType(contentType);
			HLogger.DEFAULT().error(e2);
		}

		response.addHeader("RepsoneFromHost", LocalMachine.getLocalHostName());
		try {
			if(resultText != null) {
				PrintWriter writer = response.getWriter();
				writer.write(resultText);
				writer.flush();
				writer.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private String trans2ResponseText(HttpServletRequest request, HttpServletResponse response, String contentType, Object resultObj) throws Exception {
		String resultText = null;
		if(resultObj instanceof ViewModel){//页面对象转为HTML文本后返回
			contentType = HttpContentType.HTML+";charset="+charset;
			response.setContentType(contentType);
			ViewModel viewModel = (ViewModel) resultObj;
			Map<String, Object> model = viewModel.getModel();
			addResponseData(request, model);
			if(resultObj instanceof FtlViewModel){
				try {
					String html = PageParse.parseViewModel((FtlViewModel) resultObj);
					resultText = html;
				} catch (Exception e) {
					resultText = e.toString();
					e.printStackTrace();
				}
			}else{
				throw new ServletException("不能识别的ViewModel:"+ resultObj.getClass().getName());
			}
		}else if(resultObj instanceof BinaryFile) {
			BinaryFile binFile = (BinaryFile) resultObj;
			File file = new File(binFile.getFilePath());
			if(file.exists()) {
				String responseFileName = binFile.getResponseFileName();
				if(responseFileName == null || responseFileName.length() == 0) {
					responseFileName = file.getName();
				}
				BufferedInputStream br = null;
				try {
					br = new BufferedInputStream(new FileInputStream(file));

					byte[] buf = new byte[1024];
					int len = 0;

					response.reset();
					response.setContentType("application/x-msdownload");

					response.setHeader("Content-Disposition", "attachment; filename=" + new String(responseFileName.getBytes("UTF-8"), "ISO8859-1" ));

					OutputStream out = response.getOutputStream();
					try{
						while((len = br.read(buf)) >0){
							out.write(buf,0,len);
						}
					}finally{
						out.close();
					}
				}catch(Exception ex) {
					throw new ServletException(ex.getMessage());
				}finally{
					if(br != null) {
						try {
							br.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					file.delete();
				}
			}
		}else if(resultObj instanceof JsFile) {
			resultText = readJsFile(response, (JsFile) resultObj);
		}else if(resultObj instanceof CssFile) {
			resultText = readCssFile(response, (CssFile) resultObj);
		}else{//JSON
			String requestJSType = request.getParameter("js_plugin_type");
			if(StringUtils.isNotBlank(requestJSType)) {
				resultObj = HttpRequestUtil.trans2JSData(requestJSType, resultObj);
			}

			if(resultObj == null){
				resultObj = new StringWraper("");//如果返回值为null,则返回一个空字符串，否则在ajax进行解析是会报null错误
			}
			if (resultObj instanceof String) {//字符串返回值需要封装为对象，否则在jquery中无法解析
				boolean wrapper = false;
				if(StringUtils.contains(contentType, "json")){//是否需要对字符串进行json封装
					wrapper = true;
				}
				if(wrapper) {
					resultObj = new StringWraper((String) resultObj);
				}
			}
			if(resultObj instanceof NativeString) {
				NativeString nativeString = (NativeString) resultObj;
				resultText = nativeString.getString();
			}else if(resultObj instanceof PageContent){
				PageContent pageContent = (PageContent) resultObj;
				resultText = pageContent.getText();
				if(!StringUtils.isEmpty(pageContent.getContentType())) {
					contentType = pageContent.getContentType();
				}
			}else if(resultObj instanceof RestException){
				contentType = HttpContentType.JSON;
				try {
					resultText = TJSON.toJSONString(resultObj);
				} catch (JsonProcessingException e) {
					e.printStackTrace();
				}
			}else if(resultObj instanceof NullValue) {
				resultText = null;
			}else {
				resultText = TJSON.toJSONString(resultObj);
			}

			String jsoncallback = request.getParameter("jsonpcallback");
			if(jsoncallback != null && jsoncallback.length()>0){
				String jsonp = jsoncallback+"("+resultText+")";
				resultText = jsonp;
			}

			if(contentType != null && !contentType.contains("charset")) {
				contentType = contentType +";charset="+charset;
			}
			response.setContentType(contentType);
		}
		return resultText;
	}

	private String readCssFile(HttpServletResponse response, CssFile resultObj) {
		String resultText;
		String contentType;
		contentType = HttpContentType.CSS+";charset="+charset;
		response.setContentType(contentType);
		CssFile cssfile = resultObj;
		resultText = cssfile.getCsscode();

		SimpleDateFormat sdf = new SimpleDateFormat("EEE d MMM yyyy HH:mm:ss 'GMT'", Locale.CHINA);
		sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT
		response.setHeader("Last-Modified", sdf.format(new Date(System.currentTimeMillis() - 1000*60*60*12)));
		response.setHeader("Expires", sdf.format(new Date(System.currentTimeMillis() + 1000*60*60*12)));
		response.setHeader("Cache-Control", "max-age=" + 60*60);//设置资源在本地缓存时间的一个值，单位为：秒(s)
		return resultText;
	}

	private String readJsFile(HttpServletResponse response, JsFile resultObj) {
		String resultText;
		String contentType;
		contentType = HttpContentType.JAVASCRIPT+";charset="+charset;
		response.setContentType(contentType);
		JsFile jsfile = resultObj;
		resultText = jsfile.getJscode();

		SimpleDateFormat sdf = new SimpleDateFormat("EEE d MMM yyyy HH:mm:ss 'GMT'", Locale.CHINA);
		sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT

		response.setHeader("Last-Modified", sdf.format(new Date(System.currentTimeMillis() - 1000*60*60*12)));
		response.setHeader("Expires", sdf.format(new Date(System.currentTimeMillis() + 1000*60*60*12)));
		response.setHeader("Cache-Control", "max-age=" + 60*60);//设置资源在本地缓存时间的一个值，单位为：秒(s)
		return resultText;
	}

	/**
	 * 设置一些常用的变量值
	 * @param request
	 * @param model 
	 * @author: wanghua
	 */
	private void addResponseData(HttpServletRequest request, Map<String, Object> model) {
		if(!model.containsKey("basePath")) {
			model.put("basePath", this.getBasePath(request));
		}
		if(!model.containsKey("_sessionId")) {
			String sessionId = "";
			if(request.getSession() != null){
				sessionId = request.getSession().getId();
			}
			model.put("_sessionId", sessionId);
		}
		if(!model.containsKey("_pageUUID"))model.put("_pageUUID", UUIDUtil.createUUID());
	}
	
	/**根据请求参数，执行响应的函数，将执行结果返回
	 * @param request
	 * @param response
	 * @return
	 * @throws Exception
	 * @author: wanghua
	 */
	private ServletResult excuteRequest(String requestType, String beanName, String methodPathName,
										HttpServletRequest request, HttpServletResponse response, String httpMethod) throws Exception {
//		long reqStartTime = System.currentTimeMillis();
		ServletResult result = new ServletResult();
		String funCode = "";


		Object handler = BeansContextUtil.getBean(beanName);
		if(handler == null) {
			response.setStatus(404);
			throw new HException("3004", "没有找到bean:" + beanName + ",请在spring-beans.xml中HttpServer节点中配置该bean");
		}
		result.setResponseDataType(requestType);
		if("view".equals(requestType)) {
			request.setAttribute("dayu_request_start_time", System.currentTimeMillis());
		}
		Method method = null;
		if("base".equals(requestType) || "vo".equals(requestType) || "view".equals(requestType) || "file".equals(requestType)
				|| "xls".equals(requestType) || "api".equals(requestType)) {			
			if(!handler.getClass().isAnnotationPresent(HController.class) && !handler.getClass().isAnnotationPresent(HttpHandler.class)){
				throw new HException(beanName+"不是一个带有HController或者HttpHandler注解的类");
			}
			
			//urlType = "JSON";
			method = BeanInvoker.findMethod(handler.getClass(), methodPathName);	
			if(method == null) {
				response.setStatus(404);
				throw new HException("请求的路径不存在:"+beanName+"/"+methodPathName);
			}
			Path pathAnno = method.getAnnotation(Path.class);
			//GET请求只能作用于Path设置为GET的请求。POST请求不做限制。
			if("GET".equalsIgnoreCase(httpMethod)){//http请求的方式为GET
				if(pathAnno.httpGet() == false){//Path注解上是否设置了GET模式
					throw new HException("ERR-0141", "此资源不支持以GET方式请求");
				}
			}
			//日志相关
			funCode = beanName + ":" + methodPathName;
			
//				if("view".equals(requestType)) {
//					urlType = "PAGE";
//				}
			//end 日志
			
			Object resultData = null;
			try {
				resultData = BeanInvoker.invokeMethod(handler, method, request, response);
			}catch(Exception ex) {
				if("api".equals(requestType)) {
					response.setStatus(500); //返回500错误
				}
				//记录日志
				HttpRequestLog requestLogContext = HttpRequestLogThreadLocal.getHttpRequestLogContext();
				String loginId = requestLogContext.getLoginId();
				String errorCode = "";
				if(!(ex instanceof HException)){
					HLogClient.logError(funCode, errorCode, ex.getMessage(), loginId);
				}
				//end 日志
				throw ex;
			}
			result.setResponseData(resultData);
			
			String contentType = HttpContentType.JSON;
			if(resultData instanceof ViewModel){
				contentType = HttpContentType.HTML;
			}else if(pathAnno != null){
				contentType = pathAnno.produces();
			}
			result.setContentType(contentType);			
		}else {
			response.setStatus(404);
			throw new HException("3003", "URL格式错误,正确的格式应该是 base[view,file,info,api]/handler/method");
		}
		
		//根据请求类型，修正返回结果
		Object resultData = result.getResponseData();
		if("base".equals(requestType)){//如果请求类型为base，而函数返回结果为ViewModel,则实际返回其Model
			if(resultData instanceof ViewModel){
				ViewModel vm = (ViewModel)resultData;
				resultData = vm.getModel();
				result.setResponseData(resultData);
			}
		}else if("view".equals(requestType)){
			if(!(resultData instanceof ViewModel)){
				response.setStatus(500);
				throw new HException("view请求必须返回一个ViewModel对象");
			}
		}else if("xls".equals(requestType)){
			Object exportData = resultData;

			HttpSession session = request.getSession();
			Long tenantId = (Long)session.getAttribute(SSOConsts.SESSION_NAME_TENANT_ID);//当前用户的租户id

			ExcelTemplate excelTemplate = method.getAnnotation(ExcelTemplate.class);

			BinaryFile responseData = HttpRequestUtil.exportXlsData(beanName, methodPathName, request, excelTemplate, exportData, tenantId);
			if(responseData != null){
				result.setResponseData(responseData);
			}
		}
//		long reqEndTime = System.currentTimeMillis();
		
		try {
			//记录日志
//			if(logUrlRequest && HLogClient.isInited()) {
//				HttpRequestLog requestLogContext = HttpRequestLogThreadLocal.getHttpRequestLogContext();
//				SqlExecuteLogs sqlLogContext = SqlExecuteLogsThreadLocal.getSqlLogContext();
//				//String tenantId = SaasTenantThreadHolder.getTenantId();
//				String loginId = requestLogContext.getLoginId();
//				int reqMills = (int)(reqEndTime - reqStartTime);
//				
//				//sql执行日志
//				List<SqlExecuteLog> sqlLogs = sqlLogContext.getLogs();
//				for(SqlExecuteLog sqlLog : sqlLogs) {
//					QLogClient.logSqlResult(funCode, sqlLog.getSqlText(), sqlLog.getMills(), sqlLog.getRows());
//				}
//				
//				int sqlCount = sqlLogContext.getSqlCount();
//				int resultRows = sqlLogContext.getTotalRows();
//				int sqlMills = sqlLogContext.getTotalMills();
//				//url请求日志
//				QLogClient.logUrlRequest(urlType, loginId, funCode, funName, reqMills, sqlCount, resultRows, sqlMills);
//				HLogger.DEFAULT().debug(funCode+":"+reqMills+"ms");
//				
//			}
		}catch(Exception ex) {
			ex.printStackTrace();
		}
		return result;
	}




	private String getBasePath(HttpServletRequest request){
		String path = request.getContextPath();
		String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
		return basePath;
	}
	
}
